package com.luxus.dev;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import com.android.camera.CaptureModuleUtil;
import com.android.camera.FatalErrorHandler;
import com.android.camera.FatalErrorHandlerImpl;
import com.android.camera.app.OrientationManager;
import com.android.camera.app.OrientationManager.DeviceOrientation;
import com.android.camera.async.MainThread;
import com.android.camera.async.Observable;
import com.android.camera.burst.BurstFacade;
import com.android.camera.burst.BurstFacadeFactory;
import com.android.camera.burst.BurstReadyStateChangeListener;
import com.android.camera.burst.OrientationLockController;
import com.android.camera.debug.Log;
import com.android.camera.one.CameraId;
import com.android.camera.one.FlashSetting;
import com.android.camera.one.OneCamera;
import com.android.camera.one.OneCamera.AutoFocusState;
import com.android.camera.one.OneCamera.CaptureReadyCallback;
import com.android.camera.one.OneCamera.FaceDetectListener;
import com.android.camera.one.OneCamera.FocusStateListener;
import com.android.camera.one.OneCamera.OpenCallback;
import com.android.camera.one.OneCamera.PhotoCaptureParameters;
import com.android.camera.one.OneCamera.PictureCallback;
import com.android.camera.one.OneCamera.ReadyStateChangedListener;
import com.android.camera.one.OneCameraAccessException;
import com.android.camera.one.OneCameraCaptureSetting;
import com.android.camera.one.OneCameraCharacteristics;
import com.android.camera.one.OneCameraManager;
import com.android.camera.one.OneCameraModule;
import com.android.camera.one.OneCameraOpener;
import com.android.camera.one.config.OneCameraFeatureConfig;
import com.android.camera.one.config.OneCameraFeatureConfigCreator;
import com.android.camera.one.v2.photo.ImageRotationCalculator;
import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl;
import com.android.camera.session.CaptureSession;
import com.android.camera.util.AndroidServices;
import com.android.camera.util.Size;
import com.luxus.app.FeatureConfig;

import android.app.Activity;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.media.MediaActionSound;
import android.net.Uri;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.DisplayMetrics;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.WindowManager;

public class CameraOperator implements SurfaceHolder.Callback {
	private static final Log.CTag TAG = new Log.CTag("CameraOperator");	

	private Activity mActivity;
	private Context mAppContext;
	private MediaActionSound mMediaActionSound;
	private SurfaceView mSurfaceView;
	private SurfaceHolder mSurfaceHolder;
	private TextureView mTextureView;
	private SurfaceTexture mSurfaceTexture;
	private int mTextureId;
	private Handler mCameraHandler;
	private FatalErrorHandler mFatalErrorHandler;
	
    private OneCameraOpener mOneCameraOpener;
    private OneCameraManager mOneCameraManager;
    private OneCameraFeatureConfig mFeatureConfig;
    private OneCameraCaptureSetting mCaptureSetting;
    private CameraId mCameraId;
    private OneCamera mCamera;
    private OneCameraCharacteristics mCameraCharacteristics;
    private DebugForCaptureFlash mDebugCapture = new DebugForCaptureFlash(FeatureConfig.useFlashSetting);
//    private DebugForCaptureRequest mDebugCapture = new DebugForCaptureRequest();
    
    private ICameraState mStateListener;
    
    private final AtomicBoolean mHasStartPreview;
    
    private BurstFacade mBurstController;
    private boolean mBursting = false;
    private OrientationManager mOrientationManager;
	
    public static class CameraConfig {
    	public CameraId mCameraId;
    	public Size mPreviewRatio;
    	public Size mPictureRatio;
    	public boolean mFaceDetectEnable;
    	public Observable<String> mFlash;
    	public Observable<Integer> mExposureSetting;
    	public Observable<Boolean> mHdrSceneSetting;
    }
	public CameraOperator(Activity activity) {
		mActivity = activity;
		mAppContext = activity.getApplicationContext();
		
		mFatalErrorHandler = new FatalErrorHandlerImpl(activity);
		mHasStartPreview = new AtomicBoolean(false);
	}
	
	public void setSurfaceView(SurfaceView surfaceView) {
		mSurfaceView = surfaceView;
		mSurfaceView.getHolder().addCallback(this);
	}
	
	public void setTextureView(TextureView textureView) {
		mTextureView = textureView;
	}
	
	public void setOrientationManager(OrientationManager orientationManager) {
		mOrientationManager = orientationManager;
	}
	
	public void init(final CameraConfig cameraConfig, ICameraState listener) {
		if (mOneCameraOpener != null) {
			return;
		}
		Log.e(TAG, "init");
		mCameraId = cameraConfig.mCameraId;
		mStateListener = listener;
		
		mBurstController = BurstFacadeFactory.create(mAppContext,
                new OrientationLockController() {
                    @Override
                    public void unlockOrientation() {}
                    @Override
                    public void lockOrientation() {}
                },
                new BurstReadyStateChangeListener() {
                    @Override
                    public void onBurstReadyStateChanged(boolean ready) {}
                });
		mBurstController.initialize(new SurfaceTexture(0));
		
		mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(mAppContext.getContentResolver(), 
				FeatureConfig.useLevel, FeatureConfig.useReprocess,
        		OneCameraFeatureConfig.HdrPlusSupportLevel.NONE, Integer.MAX_VALUE);
        
        try {
        	mOneCameraManager = OneCameraModule.provideOneCameraManager();
            mOneCameraOpener  = OneCameraModule.provideOneCameraOpener(
                    mFeatureConfig, mAppContext, getDisplayMetrics());
            
            mCameraCharacteristics =
                    mOneCameraManager.getOneCameraCharacteristics(cameraConfig.mCameraId);
    		List<Size> previewSizes = mCameraCharacteristics.getSupportedPreviewSizes();
    		Size previewRatio = cameraConfig.mPreviewRatio;
    		float aspectRatio = previewRatio.getWidth() / (float) previewRatio.getHeight();
    		Size previewSize = CaptureModuleUtil.getOptimalPreviewSize(
                    (Size[]) previewSizes.toArray(new Size[previewSizes.size()]),
                    aspectRatio, null);
    		if (previewSizes.contains(previewRatio)) {
    			previewSize = previewRatio;
    		}
    		List<Size> picSizes = mCameraCharacteristics.getDefaultPictureSizes(ImageFormat.JPEG);
    		Size pictureSize = getMaxSize(picSizes, cameraConfig.mPictureRatio);
    		if (picSizes.contains(cameraConfig.mPictureRatio)) {
    			pictureSize = cameraConfig.mPictureRatio;
    		}
    		List<Size> HRSizes  = mCameraCharacteristics.getHighResolutionPictureSizes(ImageFormat.JPEG);
    		if (HRSizes.contains(cameraConfig.mPictureRatio)) {
    			pictureSize = cameraConfig.mPictureRatio;
    		}
    		
    		mCaptureSetting = OneCameraCaptureSetting.create(previewSize,
    				pictureSize, new FlashSetting(cameraConfig.mFlash), 
    				cameraConfig.mExposureSetting, cameraConfig.mHdrSceneSetting, false);		
    		
			Log.e(TAG, "diaplay setFixedSize "+previewSize.getWidth()+"x"+previewSize.getHeight());
			if (mTextureView != null) {
				int[] textures = new int[1];
				GLES20.glGenTextures(1, textures, 0);
				mTextureId = textures[0];
				mSurfaceTexture = new SurfaceTexture(textures[0]);
				mSurfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
				mSurfaceTexture.detachFromGLContext();
				if (FeatureConfig.useSurfaceType == FeatureConfig.WITH_TEXTUREVIEW) {
					mTextureView.setSurfaceTexture(mSurfaceTexture);
				}
			} else if (mSurfaceView != null){
				mSurfaceView.getHolder().setFixedSize(previewSize.getWidth(), previewSize.getHeight());
			}
        } catch (Exception e) {
            Log.e(TAG, "Creating camera manager failed.", e);
            return;
        }
        
		HandlerThread thread = new HandlerThread("CameraOperator.mCameraHandler");
        thread.start();
        mCameraHandler = new Handler(thread.getLooper());
        mCameraHandler.post(new Runnable() {
        	public void run() {                
                try {
                	openCamera(cameraConfig);
                } catch (OneCameraAccessException ex) {
                	Log.e(TAG, "open camera failed.", ex);
                }
        	}
        });
	}
	
	public OneCameraCharacteristics getCharacteristics() {
		return mCameraCharacteristics;
	}
	
	public void snap(CaptureSession session) {
		Log.e(TAG, "snap");
		int orientation = mOrientationManager.getDeviceOrientation().getDegrees();
		PhotoCaptureParameters params = new PhotoCaptureParameters(
                session.getTitle(), orientation, session.getLocation(),
                mAppContext.getExternalCacheDir(), mPictureCallback, mPictureSaverCallback,
                0, 1.0f, 0);
		
		mCamera.takePicture(params, session, mDebugCapture);
	}
	
	public void startBurst(
			CaptureSession.CaptureSessionCreator captureSessionCreator) {
		if (mBursting) return;
		Log.e(TAG, "startBurst");
		DeviceOrientation deviceOrientation = mOrientationManager.getDeviceOrientation();
		ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
                .from(mOrientationManager, mCameraCharacteristics);
		
		mBurstController.startBurst(captureSessionCreator, 
				deviceOrientation, mCamera.getDirection(), 
				imageRotationCalculator.toImageRotation().getDegrees());
		mBursting = true;
	}
	
	public void stopBurst() {
		if (!mBursting) return;
		Log.e(TAG, "stopBurst");
		mBurstController.stopBurst();
		mBursting = false;
	}
	
	public void deinit(boolean wait) {
		if (mOneCameraOpener == null) return;
		Log.e(TAG, "deinit");
		mBurstController.release();
		if (mSurfaceView != null) {
			mSurfaceView.getHolder().removeCallback(this);
		}
		mCameraHandler.post(new Runnable() {
			public void run() {
				closeCamera();
			}
		});
		if (wait) {
			waitDone(mCameraHandler);
		}
		
		if (mSurfaceTexture != null) {
			int[] textures = new int[1];
			textures[0] = mTextureId;
			GLES20.glDeleteTextures(1, textures, 0);
			mSurfaceTexture.release();
			mSurfaceTexture = null;
		}
		
		mOneCameraOpener = null;
		mCameraHandler.getLooper().quitSafely();
		mMediaActionSound.release();
		mMediaActionSound = null;
		mStateListener = null;
	}
	
	private void openCamera(final CameraConfig cameraConfig) throws OneCameraAccessException {
		if (mOneCameraOpener == null) {
			return;
		}
		Log.e(TAG, "openCamera E");
		MainThread mainThread = MainThread.create();		
		
		ImageRotationCalculator imageRotationCalculator = 
				ImageRotationCalculatorImpl.from(mOrientationManager, mCameraCharacteristics);
		
		mOneCameraOpener.open(cameraConfig.mCameraId, mCaptureSetting, mCameraHandler, mainThread, 
				imageRotationCalculator, mBurstController, new OpenCallback() {
					@Override
					public void onCameraOpened(OneCamera camera) {
						Log.e(TAG, "onCameraOpened E");
						mCamera = camera;
						mActivity.runOnUiThread(new Runnable() {
							public void run() {
								if (mStateListener != null) {
									mStateListener.onCameraOpened(mCameraId);
								}
							}
						});
						mCamera.enableFaceDetect(cameraConfig.mFaceDetectEnable);
						startPreview();
						
						mMediaActionSound = new MediaActionSound();
				        mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK);
				        Log.e(TAG, "onCameraOpened X");
					}

					@Override
					public void onFailure() {
						closeCamera();
					}

					@Override
					public void onCameraClosed() {
						mCamera = null;
					}
		}, mFatalErrorHandler);
		Log.e(TAG, "openCamera X");
	}
	
	private void startPreview() {
		if (FeatureConfig.useSurfaceType != FeatureConfig.WITH_NODISPLAY
				&& mSurfaceHolder == null && mTextureView == null) return;
		if (mCamera == null) return;
		if (mHasStartPreview.getAndSet(true)) return;
		Surface previewSurface = null;
		if (mSurfaceHolder != null) {
			previewSurface = mSurfaceHolder.getSurface();
		} else if (mSurfaceTexture != null) {
			previewSurface = new Surface(mSurfaceTexture);
		}
		Log.e(TAG, "startPreview E");
		mCamera.startPreview(previewSurface,
                new CaptureReadyCallback() {
					@Override
					public void onReadyForCapture() {
						Log.e(TAG, "onReadyForCapture");
						mActivity.runOnUiThread(new Runnable() {
							public void run() {
								if (mStateListener != null) {
									mStateListener.onPreviewReady(mCameraId);
								}
							}
						});
						mCamera.setReadyStateChangedListener(new ReadyStateChangedListener() {
							@Override
							public void onReadyStateChanged(boolean readyForCapture) {
								Log.e(TAG, "onReadyStateChanged "+readyForCapture);
							}
						});
						mCamera.setFocusStateListener(new FocusStateListener() {
							@Override
							public void onFocusStatusUpdate(AutoFocusState state, long frameNumber) {
								Log.e(TAG, "onFocusStatusUpdate "+state);
							}
						});
						mCamera.addMetadataListener(mDebugCapture);
					}

					@Override
					public void onSetupFailed() {
						Log.e(TAG, "onSetupFailed");
						closeCamera();
					}
		});
		Log.e(TAG, "startPreview X");
		mActivity.runOnUiThread(new Runnable() {
			public void run() {
				if (mStateListener != null) {
					mStateListener.onPreviewStarted(mCameraId);
				}
			}
		});
	}
	
	private void closeCamera() {
		if (mCamera != null) {
			mCamera.removeMetadataListener(mDebugCapture);
			mCamera.setFaceDetectListener(null);
			Log.e(TAG, "closeCamera E");
            mCamera.close();
            mCamera = null;
            Log.e(TAG, "closeCamera X");
            
            mHasStartPreview.set(false);
            final ICameraState listener = mStateListener;
            mActivity.runOnUiThread(new Runnable() {
				public void run() {
					if (listener != null) {
						listener.onCameraClosed(mCameraId);
					}
				}
			});
		}
	}
	
	public void setFaceDetectListener(final FaceDetectListener listener) {
		if (mCamera == null) return;
		mCamera.setFaceDetectListener(listener);
	}
	
	private final OneCamera.PictureSaverCallback mPictureSaverCallback =
            new OneCamera.PictureSaverCallback() {
                @Override
                public void onRemoteThumbnailAvailable(final byte[] jpegImage) {
                	Log.e(TAG, "onRemoteThumbnailAvailable E");
                }
            };
            
    private final PictureCallback mPictureCallback = new PictureCallback() {

    	public void onPreflash() {
    		
    		if (mStateListener != null) {
				mStateListener.onCapturePreflash(mCameraId);
			}
    	}
    	
		@Override
		public void onQuickExpose() {
			Log.e(TAG, "onQuickExpose E");
			if (mMediaActionSound != null) {
				mMediaActionSound.play(MediaActionSound.SHUTTER_CLICK);
			}
			if (mStateListener != null) {
				mStateListener.onCaptureCompleted(mCameraId);
			}
		}

		@Override
		public void onThumbnailResult(byte[] jpegData) {
			Log.e(TAG, "onThumbnailResult E");
		}

		@Override
		public void onPictureTaken(CaptureSession session) {
			Log.e(TAG, "onPictureTaken E");
		}

		@Override
		public void onPictureSaved(Uri uri) {
			Log.e(TAG, "onPictureSaved E");
		}

		@Override
		public void onPictureTakingFailed() {
			Log.e(TAG, "onPictureTakingFailed E");
		}

		@Override
		public void onTakePictureProgress(float progress) {
			Log.e(TAG, "onTakePictureProgress "+progress);
		}
    	
    };

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		Log.e(TAG, "surfaceCreated");
		mSurfaceHolder = holder;
		mCameraHandler.post(new Runnable() {
			public void run() {
				startPreview();
			}
		});
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		mSurfaceHolder = null;
	}
	
	public static boolean waitDone(Handler handler) {
        Log.d(TAG, "waitDone E");
        final Object waitDoneLock = new Object();
        final Runnable unlockRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (waitDoneLock) {
                    Log.d(TAG, "waitDone notifyAll");
                    waitDoneLock.notifyAll();
                }
            }
        };

        synchronized (waitDoneLock) {
        	handler.post(unlockRunnable);
            try {
                waitDoneLock.wait();
            } catch (InterruptedException ex) {
                Log.v(TAG, "waitDone interrupted");
                return false;
            }
        }
        Log.d(TAG, "waitDone X");
        return true;
    }    
	
	public OneCameraCaptureSetting getCaptureSetting() {
		return mCaptureSetting;
	}
	
	private static DisplayMetrics getDisplayMetrics() {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        WindowManager wm = AndroidServices.instance().provideWindowManager();
        if (wm != null) {
            wm.getDefaultDisplay().getMetrics(displayMetrics);
        }
        return displayMetrics;
    }
	
	private static Size getMaxSize(List<Size> list, Size ratioSize) {
		int max = 0;
		Size target = new Size(0, 0);
		for (Size temp : list) {
			Log.e(TAG, "luxus pictureSize "+temp.getWidth()+"x"+temp.getHeight());
			if (Math.abs(temp.getHeight() * ratioSize.getWidth() 
					- temp.getWidth() * ratioSize.getHeight())  < 1024) {
				int maxTemp = temp.getHeight() * temp.getWidth();
				if (maxTemp > max) {
					target = temp;
					max = maxTemp;
				}
			}
		}
		return target;
	}
}
